package com.tus.storage;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.AbstractMap;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
public class LocalFileStorage implements FileStorage {

    private final Path storageRoot;

    public LocalFileStorage(String rootPath) throws IOException {
        this.storageRoot = Paths.get(rootPath).toAbsolutePath().normalize();
        Files.createDirectories(storageRoot);
        log.info("TUS File Storage initialized at: {}", storageRoot);
    }


    @Override
    public String createFile(String uploadId, long fileSize) throws IOException {
        return createFile(uploadId, "data", fileSize);
    }

    /**
     * 创建主上传文件（串行模式）
     */
    public String createFile(String uploadId, String fileName, long fileSize) throws IOException {
        Path fileDir = storageRoot.resolve(uploadId);
        Files.createDirectories(fileDir);

        Path filePath = fileDir.resolve(fileName);
        // 创建指定大小的空文件（稀疏文件）
        try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "rw")) {
            raf.setLength(fileSize);
        }
        return filePath.toString();
    }

    /**
     * 为并发上传创建分片目录
     */
    public String createChunkDirectory(String uploadId) throws IOException {
        Path chunkDir = storageRoot.resolve(uploadId).resolve("chunks");
        Files.createDirectories(chunkDir);
        return chunkDir.toString();
    }

    /**
     * 写入分片文件
     */
    public String writeChunkFile(String uploadId, String filename, byte[] data) throws IOException {
        Path chunkDir = storageRoot.resolve(uploadId).resolve("chunks");
        Path chunkPath = chunkDir.resolve(filename);

        try (OutputStream os = Files.newOutputStream(chunkPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
            os.write(data);
        }
        log.debug("Wrote chunk: {} ({} bytes)", chunkPath, data.length);
        return chunkPath.toString();
    }

    /**
     * 获取分片文件大小
     */
    public long getChunkSize(String chunkPathStr) {
        Path path = Paths.get(chunkPathStr);
        try {
            return Files.size(path);
        } catch (IOException e) {
            log.warn("Failed to get size of chunk {}: {}", chunkPathStr, e.getMessage());
            return 0;
        }
    }

    /**
     * 合并分片到最终文件
     * 按 offset 排序后写入
     */
    public void mergeChunks(List<String> chunkPaths, String finalPathStr, Map<String, Long> chunkOffsets) throws IOException {
        Path finalPath = Paths.get(finalPathStr);
        Path parent = finalPath.getParent();
        if (parent != null) {
            Files.createDirectories(parent);
        }

        // 按偏移量排序
        List<Map.Entry<String, Long>> sorted = chunkPaths.stream().map(path -> new AbstractMap.SimpleEntry<>(path, chunkOffsets.get(path))).sorted(Map.Entry.comparingByValue()).collect(Collectors.toList());

        try (FileChannel outChannel = FileChannel.open(finalPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
            for (Map.Entry<String, Long> entry : sorted) {
                String chunkPathStr = entry.getKey();
                Path chunkPath = Paths.get(chunkPathStr);

                try (FileChannel inChannel = FileChannel.open(chunkPath, StandardOpenOption.READ)) {
                    inChannel.transferTo(0, inChannel.size(), outChannel);
                }
                log.debug("Merged chunk: {} -> offset {}", chunkPathStr, outChannel.position());
            }
        }
        log.info("✅ Merged {} chunks into: {}", sorted.size(), finalPathStr);
    }

    /**
     * 清理分片目录（上传成功或失败后调用）
     */
    public void cleanupChunks(String uploadId) throws IOException {
        Path chunkDir = storageRoot.resolve(uploadId).resolve("chunks");
        if (Files.exists(chunkDir)) {
            try (Stream<Path> stream = Files.walk(chunkDir)) {
                stream.sorted(Comparator.reverseOrder()).forEach(path -> {
                    try {
                        Files.delete(path);
                    } catch (IOException e) {
                        log.warn("Failed to delete: {}", path);
                    }
                });
            }
            log.debug("Cleaned up chunks for upload: {}", uploadId);
        }
    }

    /**
     * 删除单个文件
     */
    public void deleteFile(String pathStr) throws IOException {
        Path path = Paths.get(pathStr);
        if (Files.exists(path)) {
            Files.delete(path);
            log.debug("Deleted file: {}", pathStr);
        }
    }

    /**
     * 追加写入（串行模式使用）
     */
    public void writeChunk(String filePathStr, long offset, byte[] data) throws IOException {
        Path path = Paths.get(filePathStr);
        try (RandomAccessFile raf = new RandomAccessFile(path.toFile(), "rw"); FileChannel channel = raf.getChannel()) {
            channel.position(offset);
            channel.write(ByteBuffer.wrap(data));
        }
    }

    @Override
    public File getFile(String filePath) {
        return new File(filePath);
    }
}